package com.pf.interceptor;

import com.pf.annotation.EnableFieldCipher;
import com.pf.annotation.FieldDecrypt;
import com.pf.utils.AESUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Value;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class))
public class DecryptFieldInterceptor implements Interceptor {

    @Value("${cipher.field.decrypt.global.switch:false}")
    private boolean decryptGlobalSwitch;

    /**
     * mybatis执行sql语句信息对象
     */
    private static final String MAPPED_STATEMENT = "mappedStatement";


    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        List<Object> result = (List<Object>) invocation.proceed();

        ResultSetHandler resultSetHandler = realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(resultSetHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(MAPPED_STATEMENT);

        // 获取目标sql方法返回的数据对象类型
        ResultMap resultMap = mappedStatement.getResultMaps().isEmpty() ? null : mappedStatement.getResultMaps().get(0);
        if (resultMap == null) {
            return result;
        }

        Class<?> resultClass = resultMap.getType();
        // 递归获取类中中含有 @FieldDecrypt 注解的属性
        Map<Field, FieldDecrypt> fieldFieldDecryptMap = new HashMap<>();
        getRecurseClassFields(fieldFieldDecryptMap, resultClass);
        if (fieldFieldDecryptMap.isEmpty()) {
            return result;
        }

        // 解密
        decryptCipherField(result, fieldFieldDecryptMap);
        return result;
    }


    @Override
    public Object plugin(Object target) {
        if (target instanceof ResultSetHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }


    /**
     * 获取真正被代理的原始对象
     * <p>
     * 拦截器代理对象可能经过多层代理，可通过该方法获取真正被代理的原始对象
     *
     * @param target
     * @param <T>
     * @return
     */
    private static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }


    /**
     * 递归获取类中中含有 @FieldDecrypt 注解的属性
     *
     * @param fieldDecryptMap
     * @param entityClass
     * @return
     */
    private void getRecurseClassFields(Map<Field, FieldDecrypt> fieldDecryptMap, Class<?> entityClass) {
        EnableFieldCipher enableFieldCipher = entityClass.getAnnotation(EnableFieldCipher.class);
        if (decryptGlobalSwitch && enableFieldCipher != null && enableFieldCipher.value()) {
            Field[] fields = entityClass.getDeclaredFields();
            if (fields != null && fields.length > 0) {
                for (Field field : fields) {
                    if (field.isAnnotationPresent(FieldDecrypt.class) && String.class.equals(field.getType())) {
                        fieldDecryptMap.put(field, field.getAnnotation(FieldDecrypt.class));
                    }
                }
            }
            Class<?> superclass = entityClass.getSuperclass();
            if (superclass != null && superclass.getDeclaredFields() != null && superclass.getDeclaredFields().length > 0) {
                getRecurseClassFields(fieldDecryptMap, superclass);
            }
        }
    }

    private void decryptCipherField(List<Object> result, Map<Field, FieldDecrypt> fieldFieldDecryptMap) {
        result.forEach(obj -> {
//            MetaObject metaObject = mappedStatement.getConfiguration().newMetaObject(obj);
            MetaObject metaObject = SystemMetaObject.forObject(obj);
            fieldFieldDecryptMap.forEach((field, fieldDecrypt) -> {
                // 给原字段赋值
                String oriFieldName = fieldDecrypt.encryptFieldName();
                if (StringUtils.isNotBlank(oriFieldName)) {
                    String value = (String) metaObject.getValue(field.getName());
                    String decryptValue = StringUtils.isNotBlank(value) ? AESUtils.decrypt(value, AESUtils.AES_KEY) : null;
                    metaObject.setValue(oriFieldName, decryptValue + "**");
                }
            });
        });
    }
}
